package ge;

import ge.standardsymbols.ConstantSymbol;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.log4j.Logger;

/**
 * A sequence of symbols
 * 
 * @author Nathaniel Troutman
 * 
 */
public class Expression {
	private static Logger LOG = Logger.getLogger(Expression.class);

	private SymbolType[] _symbol_types = null;
	private Symbol[] _symbols;
	private boolean _conatinsNonTerminal;
	private int _max_termination_depth = -1;

	private Integer[] _indices_used;

	private Expression() {
		// Do nothing
	}

	/**
	 * Defines an expression as the sequence of symbol types.
	 * 
	 * @param symbol_types
	 *            the symbol type objects that make up this expression.
	 */
	public Expression(Object[] symbol_types) {
		_symbol_types = new SymbolType[symbol_types.length];

		for (int i = 0; i < symbol_types.length; i++) {

			if (symbol_types[i] == null) {
				LOG.fatal("A null 'symbol' has been found.",
						new NullPointerException());
				throw new NullPointerException();
				// System.exit(0);
			}

			if (symbol_types[i] instanceof ConstantSymbol) {
				_symbol_types[i] = new SymbolType(
						(ConstantSymbol) symbol_types[i]);
			} else if (symbol_types[i] instanceof Class<?>) {
				_symbol_types[i] = new SymbolType((Class<?>) symbol_types[i]);
			} else if (symbol_types[i] instanceof SymbolType) {
				_symbol_types[i] = (SymbolType) symbol_types[i];
			}
		}

		setContainsNonTerminal();
	}

	public Expression(Object[] symbol_types, Integer[] indicies_used) {
		this(symbol_types);
		_indices_used = indicies_used;
	}

	/**
	 * Creates an expression where all the symbols have instatianed. By default
	 * expressions only contain type information.
	 * 
	 * @return
	 */
	public Expression createInstance() {
		if (_symbol_types == null)
			createClassList();

		Expression exp = new Expression();
		exp._symbols = new Symbol[_symbol_types.length];

		for (int i = 0; i < _symbol_types.length; i++) {
			if (_symbol_types[i].is_constant) {
				exp._symbols[i] = _symbol_types[i]._constant_symbol;
			} else {
				try {
					exp._symbols[i] = (Symbol) _symbol_types[i].clazz
							.newInstance();
					exp._symbols[i].setIndex(_indices_used[i]);
				} catch (Exception e) {
					LOG.error("Error creating instance", e);
				}
			}
		}

		exp.setContainsNonTerminal();

		return exp;
	}

	/**
	 * Attempt to resolve the expression by applying production rules until
	 * there are only terminals left.
	 * 
	 * @param indices
	 * @param depth
	 * @return
	 */
	public Expression resolve(Iterator<Integer> indices, int index_used,
			int depth) {
		List<SymbolType> symbols = new ArrayList<SymbolType>();
		List<Integer> indices_used = new ArrayList<Integer>();

		// System.out.print("\nExp[");

		for (SymbolType symbol_type : _symbol_types) {
			// If its a non-terminal then we must resolve it
			if (symbol_type.is_nonterminal) {
				Expression exp = symbol_type.resolve(indices, depth);

				if (exp != null) {
					for (int i = 0; i < exp._symbol_types.length; i++) {
						symbols.add(exp._symbol_types[i]);
						indices_used.add(exp._indices_used[i]);
					}
				}
			} else {
				// terminals we can just add to the list
				symbols.add(symbol_type);
				indices_used.add(index_used);
			}
		}

		// for (SymbolType symbol : symbols) {
		// System.out.print(symbol._constant_symbol.getCode());
		// }
		// System.out.println(']');

		return new Expression(symbols.toArray(), indices_used
				.toArray(new Integer[] {}));
	}

	protected void createClassList() {
		SymbolType[] symbol_types = new SymbolType[_symbols.length];

		for (int i = 0; i < symbol_types.length; i++) {
			_symbol_types[i] = new SymbolType(_symbols[i].getClass());
		}

		_symbol_types = symbol_types;
	}

	public String toString() {
		StringBuilder str = new StringBuilder();

		for (int i = 0; i < _symbol_types.length; i++) {
			SymbolType type = _symbol_types[i];
			if (type.is_constant) {
				str.append("\"");
				str.append(type._constant_symbol.getCode());
				str.append("\" ");
			} else {
				str.append("<");
				str.append(type.clazz.getSimpleName());
				if (_indices_used != null) {
					str.append(":");
					str.append(_indices_used[i]);
				}
				str.append("> ");
			}
		}
		str.append("[");
		str.append(_max_termination_depth);
		str.append("] ");
		return str.toString();
	}

	public boolean isInstance() {
		return (_symbols != null);
	}

	public String getCode() {
		StringBuilder str = new StringBuilder();

		if (_symbols != null) {
			for (Symbol symbol : _symbols) {
				str.append(((Terminal) symbol).getCode());
			}
		}

		return str.toString();
	}

	/**
	 * Does this expression contain any nonterminal symbols?
	 * 
	 * @return
	 */
	public boolean containsNonTerminal() {
		return _conatinsNonTerminal;
	}

	/**
	 * Checks the symbols to see if anyone of them are nonterminal.
	 */
	private void setContainsNonTerminal() {
		_conatinsNonTerminal = false;

		if (_symbol_types != null) {
			for (int i = 0; i < _symbol_types.length && !_conatinsNonTerminal; i++) {
				_conatinsNonTerminal = _symbol_types[i].is_nonterminal;
			}
		} else {
			for (int i = 0; i < _symbols.length && !_conatinsNonTerminal; i++) {
				_conatinsNonTerminal = (_symbols[i] instanceof NonTerminal);
			}
		}
	}

	/**
	 * Gets the number of symbols used in this expression.
	 * 
	 * @return
	 */
	public int getLength() {
		int length = 0;

		if (_symbol_types != null) {
			length = _symbol_types.length;
		} else if (_symbols != null) {
			length = _symbols.length;
		}

		return length;
	}

	/**
	 * Return the maximum of the termination depths of each symbol in this
	 * expression.
	 * 
	 * @return
	 */
	public int maxTerminationDepth() {
		// LOG.trace("maxTermDeath " + this + "=" + _max_termination_depth);

		if (_max_termination_depth == -1) {
			_max_termination_depth = Integer.MIN_VALUE;
			int tmp = _max_termination_depth;
			for (SymbolType sym_type : _symbol_types) {
				if (sym_type.is_nonterminal) {
					ProductionRule rule = NonTerminal
							.getAssociatedProductionRule(sym_type.clazz);
					if (rule == null) {
						LOG
								.fatal(
										"There is no production rule for the symbol ["
												+ sym_type.clazz
														.getSimpleName() + "]!",
										new NullPointerException());
						System.exit(0);
					}
					int min_depth = rule.getMinimumTerminationDepth();

					if (min_depth == Integer.MAX_VALUE) {
						min_depth = Integer.MAX_VALUE;
					}

					tmp = Math.max(tmp, min_depth);
				} else {
					tmp = Math.max(tmp, 0);
				}
			}
			_max_termination_depth = tmp;
		}

		return _max_termination_depth;
	}
}
